package com.nth.android.image;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap.CompressFormat;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.widget.ImageView;

import com.nth.android.CacheMap;
import com.nth.android.Util;

/**
 * Title: AndroidFramework <br />
 * Copyright: Copyright (c) 2009 <br />
 * NTH Company: NTH d.o.o. <br />
 * 
 * @author Tomica Bosnjak
 * @version 1.0.0
 * @since 1.0.0
 */
public class ImageManager {

    private static final int CACHE_SIZE = 50;
    private static final boolean CACHE_MODE = true;
    private static final int MODE_WORLD_READABLE = 0;
    private static final int IMAGE_QUALITY = 100;

    Context context = null;

    private Map<String, Bitmap> urlToBitmap = new CacheMap<String, Bitmap>(ImageManager.CACHE_SIZE, ImageManager.CACHE_MODE);
    private Queue<Group> queue = new LinkedList<Group>();

    private DownloadThread downloadThread = null;
    private Bitmap missing = null;
    private Bitmap loading = null;
    private Bitmap missingBig = null;
    private Bitmap loadingBig = null;
    private boolean busy = false;

    protected int connTimeout = Util.HTTP_CONNECT_TIMEOUT;
    protected int readTimeout = Util.HTTP_READ_TIMEOUT;

    private static ImageManager instance = null;

    /**
     * 
     * getter method for ImageManager, if instance is null new ImageManager is
     * created
     * 
     * @return instance
     */
    public static ImageManager getInstance() {
	if (instance == null) {
	    instance = new ImageManager();
	}
	return instance;
    }

    private ImageManager() {
	super();
    }

    /**
     * Check if all images for different states have been initialized as well as
     * context
     * 
     * @return boolean
     */
    public boolean isInitialized() {
	return (missing != null) && (loading != null) && (missingBig != null) && (loadingBig != null) && (context != null);
    }

    /**
     * 
     * setter method for missing bitmap
     * 
     * @param bitmap
     */
    public void setMissingBitmap(Bitmap bitmap) {
	missing = bitmap;
    }

    /**
     * 
     * setter method for loading bitmap
     * 
     * @param loading
     */
    public void setLoadingBitmap(Bitmap loading) {
	this.loading = loading;
    }

    /**
     * 
     * setter method for missing bitmap used for full screen mode
     * 
     * @param missingBig
     */

    public void setMissingBitmapBig(Bitmap missingBig) {
	this.missingBig = missingBig;
    }

    /**
     * 
     * setter method for loading bitmap used for full screen mode
     * 
     * @param loadingBig
     */
    public void setLoadingBitmapBig(Bitmap loadingBig) {
	this.loadingBig = loadingBig;
    }

    /**
     * 
     * setter method for context
     * 
     * @param context
     */
    public void setContext(Context context) {
	this.context = context;
    }

    /**
     * 
     * setter method for connection timeout
     * 
     * @param connTimeout
     */
    public void setConnTimeout(int connTimeout) {
	this.connTimeout = connTimeout;
    }

    /**
     * 
     * setter method for read timeout
     * 
     * @param readTimeout
     */
    public void setReadTimeout(int readTimeout) {
	this.readTimeout = readTimeout;
    }

    /**
     * 
     * getter method for bitmap on specified url
     * 
     * @param url
     * @return Bitmap
     */
    public Bitmap get(String url) {
	return urlToBitmap.get(url);
    }

    /**
     * 
     * loads image from url and shows it on specified imageView, cached by
     * default, isn't saved locally, doesn't have bigger version for missing
     * state
     * 
     * @param imageView
     * @param url
     */
    public void load(ImageView imageView, String url) {
	load(imageView, url, true, false, false);
    }

    /**
     * loads image from url and shows it on specified imageView, cached by
     * default, isn't saved localy, missingBig optional
     * 
     * @param imageView
     * @param url
     * @param big
     */
    public void load(ImageView imageView, String url, boolean big) {
	load(imageView, url, true, false, big);
    }

    /**
     * 
     * loads image from url and shows it on specified imageView, cache optional,
     * save to file option, missing big not required, set to false by default
     * 
     * @param imageView
     * @param url
     * @param cache
     * @param saveToFile
     */
    public void load(ImageView imageView, String url, boolean cache, boolean saveToFile) {
	load(imageView, url, cache, saveToFile, false);
    }

    /**
     * 
     * loads image from url and shows it on specified imageView, cache, save and
     * big missing image optional
     * 
     * @param imageView
     * @param url
     * @param cache
     * @param saveToFile
     * @param big
     */
    public void load(ImageView imageView, String url, boolean cache, boolean saveToFile, boolean big) {
	if (imageView == null) {
	    Log.w(ImageManager.class.getName(), "ImageView not specified!");
	    return;
	} else if (TextUtils.isEmpty(url)) {
	    imageView.setImageBitmap(big ? missingBig : missing);
	    return;
	}

	Log.i(ImageManager.class.getName(), "Loading image from " + url);

	Bitmap bitmap = urlToBitmap.get(url);
	if (bitmap != null) {
	    imageView.setImageBitmap(bitmap);
	} else {
	    bitmap = loadBitmap(Util.getFileNameFromURL(url));
	    if (bitmap != null) {
		urlToBitmap.put(url, bitmap);
		imageView.setImageBitmap(bitmap);
	    } else {
		imageView.setImageBitmap(big ? loadingBig : loading);
		queue(imageView, url, cache, saveToFile, big);
	    }
	}
    }

    /**
     * 
     * queue for loading images, starts loading next picture when queued
     * 
     * @param imageView
     * @param url
     * @param cache
     * @param saveToFile
     * @param big
     */
    public void queue(ImageView imageView, String url, boolean cache, boolean saveToFile, boolean big) {
	synchronized (queue) {
	    Iterator<Group> it = queue.iterator();
	    while (it.hasNext()) {
		if (it.next().getImageView().equals(imageView)) {
		    it.remove();
		    break;
		}
	    }

	    queue.add(new Group(imageView, url, cache, saveToFile, big));
	}

	loadNext();
    }

    /**
     * 
     * loads next image also double checks image availability and starts
     * downloadThread
     */

    private void loadNext() {
	synchronized (queue) {
	    Iterator<Group> it = queue.iterator();
	    if (!busy && it.hasNext()) {
		busy = true;
		Group group = it.next();
		it.remove();

		/* double check image availability */
		Bitmap bitmap = urlToBitmap.get(group.getUrl());
		if (bitmap != null) {
		    group.getImageView().setImageBitmap(bitmap);
		    busy = false;
		    loadNext();
		} else {
		    downloadThread = new DownloadThread(group);
		    downloadThread.start();
		}
	    }
	}
    }

    /**
     * 
     * sets image when loaded and starts download of next image
     */
    protected void onLoad() {
	if (downloadThread != null) {
	    Group group = downloadThread.getGroup();
	    if (group.getBitmap() != null) {
		group.getImageView().setImageBitmap(group.getBitmap());
	    } else if ((missing != null) && !group.isBig()) {
		group.getImageView().setImageBitmap(missing);
	    } else if ((missingBig != null) && group.isBig()) {
		group.getImageView().setImageBitmap(missingBig);
	    }

	    if (group.isCache() && (group.getBitmap() != null)) {
		if (group.isSaveToFile()) {
		    saveBitmap(group.getBitmapName(), group.getBitmap());
		}
		urlToBitmap.put(group.getUrl(), group.getBitmap());
	    }
	}
	downloadThread = null;
	busy = false;
	loadNext();
    }

    /**
     * 
     * Save bitmap with specified name, supports png and jpeg images
     * 
     * @param bitmapName
     * @param bitmap
     */
    public void saveBitmap(String bitmapName, Bitmap bitmap) {
	if ((context == null) || TextUtils.isEmpty(bitmapName) || (bitmap == null)) {
	    return;
	}

	FileOutputStream fos = null;
	try {
	    fos = context.openFileOutput(bitmapName, ImageManager.MODE_WORLD_READABLE);
	    if (bitmapName.toLowerCase().endsWith("png")) {
		bitmap.compress(CompressFormat.PNG, ImageManager.IMAGE_QUALITY, fos);
	    } else {
		bitmap.compress(CompressFormat.JPEG, ImageManager.IMAGE_QUALITY, fos);
	    }
	} catch (Exception ex) {
	    Log.e(ImageManager.class.getName(), ex.toString());
	} finally {
	    if (fos != null) {
		try {
		    fos.flush();
		} catch (IOException ex) {
		    // DO nothing
		}
	    }
	    if (fos != null) {
		try {
		    fos.close();
		} catch (IOException ex) {
		    // DO nothing
		}
	    }
	}
    }

    /**
     * 
     * loads bitmap locally
     * 
     * @param bitmapName
     * @return Bitmap or null if context or bitmapName is not specified
     */
    public Bitmap loadBitmap(String bitmapName) {
	if ((context == null) || TextUtils.isEmpty(bitmapName)) {
	    return null;
	}

	FileInputStream fis = null;
	try {
	    fis = context.openFileInput(bitmapName);
	    return BitmapFactory.decodeStream(fis);
	} catch (Exception ex) {
	    return null;
	} finally {
	    if (fis != null) {
		try {
		    fis.close();
		} catch (IOException ex) {
		    // DO nothing
		}
	    }
	}
    }

    /**
     * 
     * method for bitmap deletion from local storage
     * 
     * @param bitmapName
     * @return boolean true if bitmap has been deleted and false if context or
     *         bitmap name has not been specified or if deletion has failed for
     *         any reason
     */
    public boolean deleteBitmap(String bitmapName) {
	if ((context == null) || TextUtils.isEmpty(bitmapName)) {
	    return false;
	}

	try {
	    return context.deleteFile(bitmapName);
	} catch (Exception ex) {
	    return false;
	}
    }

    /**
     * 
     * clears queue and disconnects downloadThread
     */
    public void cancel() {
	clearQueue();
	if (downloadThread != null) {
	    downloadThread.disconnect();
	    downloadThread = null;
	}
    }

    /**
     * 
     * clears queue by overwriting it with new LinkedList
     */
    public void clearQueue() {
	synchronized (queue) {
	    queue = new LinkedList<Group>();
	}
    }

    /**
     * 
     * clears cache by overwriting it with new CacheMap
     */
    public void clearCache() {
	synchronized (((CacheMap<String, Bitmap>) urlToBitmap).getSyncObject()) {
	    urlToBitmap = new CacheMap<String, Bitmap>(ImageManager.CACHE_SIZE, ImageManager.CACHE_MODE);
	}
    }

    /**
     * 
     * Class for downloading images and saving them in group class
     * 
     * @author Tomica Bosnjak
     * 
     */
    private class DownloadThread extends Thread {

	private final Handler threadHandler = new Handler();
	private final Runnable threadCallBack = new Runnable() {

	    public void run() {
		onLoad();
	    }
	};
	private HttpURLConnection connection = null;
	private Group group = null;

	public DownloadThread(Group group) {
	    this.group = group;
	}

	@Override
	public void run() {
	    Bitmap bitmap = downloadBitmap(group.getUrl());
	    if (bitmap == null) {
		bitmap = downloadBitmap(group.getUrl());
		if (bitmap == null) {
		    bitmap = downloadBitmap(group.getUrl());
		}
	    }
	    group.setBitmap(bitmap);

	    threadHandler.post(threadCallBack);
	}

	public Bitmap downloadBitmap(String url) {
	    InputStream inStream = null;
	    String fileName = Util.getFileNameFromURL(url);
	    try {
		connection = (HttpURLConnection) new URL(url).openConnection();
		connection.setConnectTimeout(connTimeout);
		connection.setReadTimeout(readTimeout);
		connection.setDoInput(true);
		connection.connect();

		inStream = connection.getInputStream();

		FileOutputStream fos = null;
		try {
		    fos = context.openFileOutput(fileName, MODE_WORLD_READABLE);
		    Util.writeFromTo(inStream, fos);
		    fos.flush();
		} finally {
		    if (fos != null) {
			try {
			    fos.close();
			} catch (IOException ex) {/**/
			}
		    }
		}

		return loadBitmap(fileName);
	    } catch (Throwable t) {
		Log.e(ImageManager.class.getName(), t.getMessage(), t);
	    } finally {
		deleteBitmap(fileName);

		if (inStream != null) {
		    try {
			inStream.close();
		    } catch (Exception ex) {
			// DO nothing
		    }
		}
		if (connection != null) {
		    connection.disconnect();
		}
	    }
	    return null;
	}

	public void disconnect() {
	    if (connection != null) {
		connection.disconnect();
	    }
	}

	public Group getGroup() {
	    return group;
	}
    }

    /**
     * 
     * Image group, holds all images associated to gallery group
     * 
     * @author Tomica Bosnjak
     * 
     */
    private class Group {

	private ImageView imageView = null;
	private String url = null;
	private Bitmap bitmap = null;
	private boolean cache;
	private boolean saveToFile;
	private boolean big;

	public Group(ImageView imageView, String url, boolean cache, boolean saveToFile) {
	    this(imageView, url, null, cache, saveToFile, false);
	}

	public Group(ImageView imageView, String url, boolean cache, boolean saveToFile, boolean big) {
	    this(imageView, url, null, cache, saveToFile, big);
	}

	public Group(ImageView imageView, String url, Bitmap bitmap, boolean cache, boolean saveToFile) {
	    this(imageView, url, bitmap, cache, saveToFile, false);
	}

	public Group(ImageView imageView, String url, Bitmap bitmap, boolean cache, boolean saveToFile, boolean big) {
	    this.imageView = imageView;
	    this.url = url;
	    this.bitmap = bitmap;
	    this.cache = cache;
	    this.saveToFile = saveToFile;
	    this.big = big;
	}

	public ImageView getImageView() {
	    return imageView;
	}

	public String getUrl() {
	    return url;
	}

	public String getBitmapName() {
	    return Util.getFileNameFromURL(url);
	}

	public Bitmap getBitmap() {
	    return bitmap;
	}

	public void setBitmap(Bitmap bitmap) {
	    this.bitmap = bitmap;
	}

	public boolean isCache() {
	    return cache;
	}

	public boolean isSaveToFile() {
	    return saveToFile;
	}

	public boolean isBig() {
	    return big;
	}
    }
}